package game;

import heroes.Hero;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.rmi.UnexpectedException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import cards.Attack;
import cards.BaGua;
import cards.Card;
import cards.Identity;

public class Client {

	public void testingMsg(String som) {
		// return;
		appendMsg(som);
	}

	LoginView myLoginView;
	// TODO remember to create the model
	ClientModel myModel;
	View myView;

	// TODO 如果游戏开始了，那么就要把 appendMsg的目标改到游戏窗口。
	// 如果游戏还没有开始，appendMsg的目标就应该是LoginView.
	private boolean login = false;
	private Socket connection;
	private BufferedReader in;
	private PrintWriter out;
	private recThread receiver;
	// client doesn't need to keep track of other playerbox, but server does
	// because the model will also keep track of who the player is.

	private String serverIP;
	private String serverPort;

	// parameters will be retrieved from the LoginView.
	public Client(LoginView myLoginView, String serverIP, String serverPort)
			throws UnknownHostException {
		if (login) {
			return;
		}
		this.myLoginView = myLoginView;
		// testingMsg("login==" + login);
		this.serverIP = serverIP;
		this.serverPort = serverPort;
		// testingMsg("before logIn()");
		logIn();
	}

	// start button in LoginView calls this
	public void start() {
		// to be finished with the open of View
		// then players will choose their heroes
		myModel = new ClientModel(this);

		myModel.myPlayerName = myLoginView.getMyName.getText();
		myModel.myLoginView = myLoginView;
	}

	private void logIn() {
		if (login) {
			return;
		}
		// testingMsg("1");
		boolean accepted = false;
		boolean refused = false;
		String line = new String("");
		try {
			// testingMsg("2");
			connection = new Socket(serverIP, Integer.parseInt(serverPort));
			in = new BufferedReader(new InputStreamReader(
					connection.getInputStream()));
			out = new PrintWriter(connection.getOutputStream(), true);
			// testingMsg("3");
			String msg1 = "L." + myLoginView.getMyName.getText();
			System.out.println("Client send: " + msg1);
			out.println(msg1);
			// testingMsg("what is wrote to server: "+msg1 );
			while (!accepted && !refused) {
				line = in.readLine();
				appendMsg("Client.readLine() is called, msg: " + line);
				if (line.charAt(0) == 'R') {
					refused = true;
				} else if (line.charAt(0) == 'A') {
					accepted = true;
				}
			}
			if (refused) {
				appendMsg("\n!!!User name already exists.\n\n");
				myLoginView.loginFailed();
			}
			if (accepted) {
				appendMsg("\n!!!Login successfully.\n\n");
				login = true;
				String[] str = line.split("\\.");

				for (int i = 1; i < str.length; i++) {
					myLoginView.userListModel.addElement(str[i]);
				}
				myLoginView.loginSucceed();
				receiver = new recThread();
			}
		} catch (ConnectException ce) {
			appendMsg("\n!!!Warning: Server not found\n\n");
			myLoginView.loginFailed();
		} catch (UnknownHostException uhe) {
			appendMsg("\n!!!Warning: Server not found\n\n");
			myLoginView.loginFailed();
		} catch (IOException ioe) {
			appendMsg("Error: " + ioe + "\n\n");
			ioe.printStackTrace();
			myLoginView.loginFailed();
		}
	}

	void quit() {
		if (!login) {
			return;
		}
		login = false;
		out.println("Q");
		try {
			connection.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void sendMsg(String line) {
		if (!login) {
			return;
		}
		System.out.println("Client send::" + line);
		out.println(line);
	}

	// chat is for loginView
	public void chatMsg(String line) {
	}

	private class recThread extends Thread {
		public recThread() {
			start();
		}

		public void run() {
			try {
				while (login) {
					final String line = in.readLine();
					System.out.println("Client receive::" + line);
					Thread listeningThread = new Thread(new Runnable() {
						String lineCopy = new String(line);

						@Override
						public void run() {
							if (lineCopy.charAt(0) == 'Q') {
								try {
									recQuitMsg(lineCopy);
								} catch (SocketException e) {
									e.printStackTrace();
								} catch (IOException e) {
									e.printStackTrace();
								}
							} else if (lineCopy.charAt(0) == 'S') {
								try {
									recStartMsg(lineCopy);
								} catch (InstantiationException e) {
									e.printStackTrace();
								} catch (IllegalAccessException e) {
									e.printStackTrace();
								} catch (ClassNotFoundException e) {
									e.printStackTrace();
								}
							} else if (lineCopy.charAt(0) == 'L') {
								recLoginMsg(lineCopy);
							} else if (lineCopy.charAt(0) == 'C') {// loginView
																	// Chatting
								recChatMsg(lineCopy);
							} else if (lineCopy.charAt(0) == 'G') {
								try {
									recGameMsg(lineCopy);
								} catch (InterruptedException e) {
									e.printStackTrace();
								}

							} else if (lineCopy.charAt(0) == 'E') {
								// TODO what is this message "E" for?
								recExistMsg(lineCopy);
							} else if (lineCopy.charAt(0) == 'D') {
								recDeckMsg(lineCopy);
							}
						}
					});
					listeningThread.start();
				}
			} catch (SocketException se) {
				appendMsg("\n!!!Warning: Disconnected from the server.\n\n");
				login = false;
				myLoginView.reset();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	// IMPORTANT appendMsg is for system massages, don't use it for user
	// chatting
	public void appendMsg(String msg) {
		if (myLoginView.active) {
			myLoginView.appendMsg(msg);
		} else {
			myView.recSysMsg(msg);
		}
	}

	private void appendChatMsg(String msg) {
		if (myLoginView.active) {
			myLoginView.appendMsg(msg);
		} else {
			myView.recMessageMsg(msg);
		}
	}

	public void recChatMsg(String msg) {
		String[] str = msg.split("\\.", 3);// be careful about the 2 here
		appendChatMsg(str[1] + ": " + str[2]);
	}

	private void addUser(final String userName) {
		myLoginView.addUserName(userName);
	}

	public void recLoginMsg(String msg) {
		String[] str = msg.split("\\.", 2);
		addUser(str[1]);
	}

	// @deprecated see recChatMsg(String msg)
	// public void recMessageMsg(String msg) {
	// myView.recMessageMsg(msg);
	// }

	// IMPORTANT no sndSpecialMsg is needed here because the sending lines are
	// already processed
	Lock watchWuXieLock = new Lock();

	public void recGameMsg(String msg) throws InterruptedException {
		String[] str = msg.split("\\.");
		if (str[1].equals("ROUNDCHANGE")) {
			myModel.playerRoundChange();
		} else if (str[1].equals("DRAWCARD")) {
			myModel.watchDrawCard(msg);

		} else if (str[1].equals("PANDIN")) {
			// if (str.length == 9) {
			// length 9
			// "G.PANDIN." + this.userName + "." + pandinMagic + "."
			// + pandinInfo + "."+effect +"." + myModel.pandinChangePossible
			try {
				myModel.watchPandin(msg);
			} catch (UnexpectedException e) {
				e.printStackTrace();
			}
			// }
		} else if (str[1].equals("CHANGEPANDIN")) {
			// length 8
			// "G.CHANGEPANDIN."
			// + every.getName() + "."
			// + this.userName + "."
			// + str[2] + "."
			// + pandinInfo
			// this will handle both situations: 1. you r the one who will
			// change pandin,
			// 2. u will watch other people change pandin.
			myModel.activatePandinChange(msg);

		}// ::033
			// else if (str[1].equals("PANDINSTAGEEND")) {
		// myModel.watchPandinEnd(str[2]);
		// }
		else if (str[1].equals("WUXIE")) {
			// this is watching other people or you wuxie
			if (str.length == 5) {
				watchWuXieLock.lock();
				myModel.watchWuxie(str[2], str[3], Boolean.parseBoolean(str[4]));
				watchWuXieLock.unlock();
			}
			// this is asking for WuXie
			else if (str.length == 4) {
				watchWuXieLock.lock();
				boolean currentlyEffective = Boolean.parseBoolean(str[3]);
				myModel.askForWuXie(str[2], currentlyEffective);
				watchWuXieLock.unlock();
			}
		} else if (str[1].equals("CLEARPROGRESS")) {
			myView.clearProgress();
		} else if (str[1].equals("GETCARD")) {
			// 如果别的玩家可见，还应该显示出来
			myModel.whoGetCard(msg);
		} else if (str[1].equals("PANDINCHANGED")) {
			myModel.pandinChanged(msg);
		} else if (str[1].equals("FIRSTROUND")) {
			myModel.firstRound();
		} else if (str[1].equals("STARTGAME")) {
			// get your four cards
			for (int i = 0; 3 * i + 2 < 14; i++) {
				myModel.myHero.getCard(Card.createCard(str[3 * i + 2],
						Integer.parseInt(str[3 * i + 3]),
						Integer.parseInt(str[3 * i + 4])));

			}
			// set other players' hand card num to be 4
			for (Player every : myModel.playerList) {
				if (!every.getName().equalsIgnoreCase(myModel.myPlayerName)) {
					// System.out.println("STARTGAME, setting player handCardNum "+
					// every.getName());
					every.getCharacter().handCardNum += 4;
				}
			}
			// change the deck information
			myModel.cardNum -= (myModel.playerList.size() * 4);
		} else if (str[1].equals("PLAYSTAGESTART")) {
			myModel.watchPlay();
		} else if (str[1].equals("PLAYSTAGEEND")) {
			myModel.watchPlayEnd(str[2]);
		} else if (str[1].equals("PLAYCARD")) {
			myModel.watchPlayCard(str[2], str[3], str[4], str[5]);
		} else if (str[1].equals("CIXIONG")) {
			// msg:: "G.CIXIONG." + sourceName +"."+ targetName
			myModel.watchCiXiong(str[2], str[3]);
		} else if (str[1].equals("CIXIONGRESPONSE")) {
			// msg::"G.CIXIONGRESPONSE." + userName+"."+ MoPai+"." +
			// Card.tostring()
			boolean moPai = Boolean.parseBoolean(str[3]);
			if (str.length == 4) {
				// msg::"G.CIXIONGRESPONSE."+ userName +".FALSE"
				myModel.watchCiXiongResponse(moPai, str[2]);
			} else {
				myModel.watchCiXiongResponse(moPai, str[2], str[4], str[5],
						str[6]);
			}
		} else if (str[1].equals("ATTACK")) {
			// "G.ATTACK.heroName.sourceName.attackNature.attackPoint.cardName.cardcolor.cardnumber"
			myModel.watchAttack(str[3], str[2], str[4], str[5], str[6], str[7],
					str[8]);
		} else if (str[1].equals("FAILTODODGE")) {
			myView.recSysMsg(str[2] + " fails to dodge the Attack");
		} else if (str[1].equals("DODGE")) {
			myModel.watchDodge(str[2]);
		}
		// VERY IMPORTANT every action in the play stage will be ended by this
		// msg. Then start a new play stage.
		else if (str[1].equals("PLAYACTIONEND")) {
			if (myModel.myPlayerName.equalsIgnoreCase(str[2])) {
				myView.lock.unlock();
			} else {
				// do nothing
			}
		} else if (str[1].equals("DAMAGE")) {
			myModel.damageReceivedBy(str[2], str[3], str[4], str[5]);
		} else if (str[1].equals("HEAL")) {
			myModel.watchHeal(str[5], null, 1);
		} else if (str[1].equals("SAVE")) {
			// TODO "G.SAVE." + targetName + "." + healNumNeeded + "."
			// + answeree
			myModel.askForSave(str[2], Integer.parseInt(str[3]), str[4]);
		} else if (str[1].equals("WATCHSAVE")) {
			myModel.watchSave(str[2], Integer.parseInt(str[3]));
		} else if (str[1].equals("DRINKSPIRIT")) {
			myModel.watchDrinkSpirit(str[2]);
		} else if (str[1].equals("XIXUE")) {
			myView.recSysMsg(str[2] + "triggeres XiXue skill.");
			myModel.recover(str[2], null, str[3]);
		} else if (str[1].equals("WIN")) {
			myModel.winLose(Integer.parseInt(str[2]));
		} else if (str[1].equals("DEAD")) {
			myModel.playerDead(str[2]);
		} else if (str[1].equals("REVIVE")) {
			myModel.playerRevived(str[2]);
		} else if (str[1].equals("DISCARDSTAGESTART")) {
			myModel.watchDiscard(str[2]);
		} else if (str[1].equals("DISCARDSTAGEEND")) {
			myModel.watchDiscardEnd(str[2]);
		} else if (str[1].equals("DISCARDCARD")) {
			myModel.watchDiscardCard(msg);
		} else if (str[1].equals("DISCARDACTIONEND")) {
			if (myModel.myPlayerName.equalsIgnoreCase(str[2])) {
				myView.lock.unlock();
			} else {
				// do nothing
			}
		} else if (str[1].equals("MOVE")) {
			myModel.watchMove(str[2], Integer.parseInt(str[3]),
					Integer.parseInt(str[4]));
		} else if (str[1].equals("BINLIANG")) {
			myModel.watchBinLiang(msg);
		} else if (str[1].equals("LEBU")) {
			myModel.watchLeBu(msg);
		} else if (str[1].equals("SHANDIAN")) {
			myModel.watchShanDian(msg);
		} else if (str[1].equals("GUOHE")) {
			myModel.watchGuoHe(msg);
		} else if (str[1].equals("GUOHECONFIRM")) {
			myModel.watchGuoHeConfirmed(msg);
		} else if (str[1].equals("SHUNSHOU")) {
			myModel.watchShunShou(msg);
		} else if (str[1].equals("SHUNSHOUCONFIRM")) {
			myModel.watchShunShouConfirmed(msg);
		} else if (str[1].equals("EQUIPHORSEMINUS")) {
			myModel.watchEquipHorseMinus(str[2], str[3], str[4], str[5]);
		} else if (str[1].equalsIgnoreCase("EQUIPARMOR")) {
			myModel.watchEquipArmor(str[2], str[3], str[4], str[5]);
		} else if (str[1].equalsIgnoreCase("EQUIPWEAPON")) {
			myModel.watchEquipWeapon(str[2], str[3], str[4], str[5]);
		} else if (str[1].equalsIgnoreCase("EQUIPHORSEPLUS")) {
			myModel.watchEquipHorsePlus(str[2], str[3], str[4], str[5]);
		} else if (str[1].equalsIgnoreCase("LOSEHORSEMINUS")) {
			myModel.watchLoseHorseMinus(str[2]);
		} else if (str[1].equalsIgnoreCase("LOSEARMOR")) {
			myModel.watchLoseArmor(str[2]);
		} else if (str[1].equalsIgnoreCase("LOSEWEAPON")) {
			myModel.watchLoseWeapon(str[2]);
		} else if (str[1].equalsIgnoreCase("LOSEHORSEPLUS")) {
			myModel.watchLoseHorsePlus(str[2]);
		} else if (str[1].equalsIgnoreCase("WUGUBEFORE")) {
			myModel.watchWuGuBeforeWuXie(msg);
		} else if (str[1].equalsIgnoreCase("WUGU")) {
			myModel.watchWuGu(msg);
		} else if (str[1].equalsIgnoreCase("WUGUCONFIRM")) {
			Card theNewlyPicked = Card.createCard(str[3],
					Integer.parseInt(str[4]), Integer.parseInt(str[5]));
			myModel.watchWuGuConfirm(str[2], theNewlyPicked);
		} else if (str[1].equalsIgnoreCase("JUEDOU")) {
			myModel.watchJueDou(str[2], str[3]);
		} else if (str[1].equalsIgnoreCase("JUEDOUCONFIRM")) {
			myModel.watchJueDouConfirm(str[2]);
		} else if (str[1].equals("FAILJUEDOU")) {
			myView.recSysMsg(str[2] + " fails to JueDou.");
		} else if (str[1].equalsIgnoreCase("WUZHONG")) {
			myModel.watchWuZhong(str[2]);
		} else if (str[1].equalsIgnoreCase("WANJIANBEFORE")) {
			myModel.watchWanJianBeforeWuXie(str[2]);
		} else if (str[1].equalsIgnoreCase("WANJIAN")) {
			myModel.watchWanJian(str[2]);
		} else if (str[1].equalsIgnoreCase("WANJIANCONFIRM")) {
			myModel.watchWanJianConfirm(str[2]);
		} else if (str[1].equalsIgnoreCase("WANJIANTENGJIA")) {
			myModel.watchWanJianTengJia(str[2]);
		} else if (str[1].equalsIgnoreCase("NANMANBEFORE")) {
			myModel.watchNanManBeforeWuXie(str[2]);
		} else if (str[1].equalsIgnoreCase("NANMAN")) {
			myModel.watchNanMan(str[2]);
		} else if (str[1].equalsIgnoreCase("NANMANCONFIRM")) {
			myModel.watchNanManConfirm(str[2]);
		} else if (str[1].equalsIgnoreCase("NANMANTENGJIA")) {
			myModel.watchNanManTengJia(str[2]);
		} else if (str[1].equalsIgnoreCase("TAOYUANBEFORE")) {
			myModel.watchTaoYuanBeforeWuXie(str[2]);
		} else if (str[1].equals("TAOYUAN")) {
			myModel.watchHeal(str[2], str[3], Integer.parseInt(str[4]));
		} else if (str[1].equals("JIEDAOINIT")) {
			myModel.watchJieDaoInit(str[2], str[3]);
		} else if (str[1].equals("JIEDAOBEFORE")) {
			myModel.watchJieDaoBeforeWuXie(str[2], str[3], str[4]);
		} else if (str[1].equals("JIEDAO")) {
			myModel.watchJieDao(str[2], str[3], str[4]);
		} else if (str[1].equals("JIEDAOWEAPON")) {
			myModel.watchJieDaoSuccess(str[2], str[3], str[4], str[5]);
		} else if (str[1].equals("TIESUOBEFORE")) {
			myModel.watchTieSuoBeforeWuXie(str[2]);
		} else if (str[1].equals("TIESUO")) {
			Card tieSuoCard = Card.createCard(str[2], Integer.parseInt(str[3]),
					Integer.parseInt(str[4]));
			myModel.watchTieSuo(str[5], tieSuoCard);
		} else if (str[1].equals("HUOGONG")) {
			myModel.watchHuoGong(str[2], str[3]);
		} else if (str[1].equals("HUOGONGCONFIRM1")) {
			myModel.watchHuoGong1(msg);
		} else if (str[1].equals("HUOGONGCONFIRM2")) {
			myModel.watchHuoGong2(msg);
		}else if(str[1].equals("HUOGONGFAIL")){
			myModel.watchHuoGongFail();
		}else if(str[1].equals("BAGUA")){
			myModel.watchBaGua(str[2]);
		} else if(str[1].equals("BAGUACONFIRM")){
			myModel.watchBaGuaConfirm(msg);
		}
	}

	public void recExistMsg(String msg) {
		replyExistMsg();
	}

	private void replyExistMsg() {
		out.println("E");
	}

	// TODO this may be deprecated.
	public void recDeckMsg(String msg) {
		String[] str = msg.split("\\.");
		if (str[1].equals("DECK")) {

		} else if (str[2].equals("DISC")) {// short for discarded

		}
	}

	// TODO game started, install the model and view
	// will also receive information about player sequence, my identity, hero
	// cards, how other people choose their heroes. And the final start signal
	// of the game.
	public void recStartMsg(String msg) throws InstantiationException,
			IllegalAccessException, ClassNotFoundException {

		// TODO we may also need a lock for client/server, in case informations
		// swarm in problematic sequence
		String str[] = msg.split("\\.");
		if (str[1].equalsIgnoreCase("START")) {
			start();
		} else if (str[1].equalsIgnoreCase("PLAYERLIST")) {
			while (myModel == null) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			myModel.playerList = new ArrayList<Player>();
			for (int i = 2; i < str.length; i++) {
				Player newPlayer = new Player(str[i]);
				if (str[i].equalsIgnoreCase(myModel.myPlayerName)) {
					myModel.myPlayer = newPlayer;
					myModel.myPlayerName = newPlayer.getName();
				}
				myModel.registerPlayers(newPlayer);
			}
			// after this, we should start the view for players
			startMyView(myModel);
			myModel.initWinningCondition();

		} else if (str[1].equalsIgnoreCase("ID")) {
			Identity newId = new Identity(Integer.parseInt(str[2]));
			while (myView == null) {
				try {
					Thread.sleep(3);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("newId: " + newId);
			// myModel.myPlayer.identity = newId;
			System.out.println("myView: " + myView);
			myView.recSysMsg("Your Identity Is" + newId.myWho);
			myView.recSysMsg(newId.myDescription);
			// TODO 后期可以加上自己身份的显示，和胜利失败时的animation
		} else if (str[1].equalsIgnoreCase("HEROCARDS")) {
			// myLoginView.testMsg("HEROCARDS message received: " + msg);
			// myView.recSysMsg("Now you can choose your hero.\n");
			ArrayList<String> heroNames = new ArrayList<String>();
			myModel.heroList = new ArrayList<Hero>();
			for (int i = 2; i < str.length; i++) {
				heroNames.add(str[i]);
			}
			while (myView == null) {
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			myView.initHeroDialog(heroNames);
			// myView.recSysMsg("Wait for all players to decide their heroes...\n");
		} else if (str[1].equalsIgnoreCase("BATTLEFIELD")) {// after hero chosen
															// is ready, show up
															// the battleField.
			// init heroes
			for (int i = 2; i < str.length; i++) {
				Hero newHero = (Hero) Class.forName("heroes." + str[i])
						.newInstance();
				// System.out.println("New hero created: "+ newHero);
				if (myModel.playerList.get(i - 2).getName()
						.equals(myModel.myPlayerName)) {
					// this will overwrite some fields for client side.
					// as the hero client chose from dialog is not the hero sent
					// back from server.
					myModel.myHero = newHero;
					newHero.setView(myView);
					myView.linkToHero(newHero);
				} else {

				}
				myModel.playerList.get(i - 2).setCharacter(newHero);
				newHero.setController(myModel.playerList.get(i - 2));
				myModel.heroList.add(newHero);
			}
			// set up the battleField.
			myView.initBattleField();
			myView.initSkillButtons();
			myView.setVisible(true);
			sendMsg("S.READY");
		} else if (true) {// draw starting hand card, 4 cards. and game starts!

		}
	}

	private void removeUser(String userName) {
		myLoginView.removeUserName(userName);

	}

	private void startMyView(Model gameModel) {
		myView = new View(gameModel);
		myView.myModel = myModel;
		myModel.myView = myView;
		// TODO get the player list from model
		// and set this list to View.
		// 玩家列表在RightPanel和BattleField换轮（玩家回合结束与开始）时会有体现，考虑一下如何写
		// View.playerRoundChange().

		// playerBox initialization is done by the constructor of RightPanel
		myView.initRightPanel();

		// myBattleField不再这里init，因为玩家还要选英雄后才init.
		// UPDATE, battlefield 不需要链表记录玩家顺序，因为rightpanel的playerBox已经记录了，
		// 而且playerbox可以连接到player,也就可以连接到hero和hero所在的square,也就可以操作战场换轮效果了，哈哈哈哈哈哈。。。。。。
		// TODO 玩家选择英雄时，服务器计数，计数达到玩家数时，可知玩家选择完毕，然后通知BattleField开始准备Hero
		// display,这里考虑是否需要保存一个链表，因为牵扯到游戏换轮问题
	}

	public void recQuitMsg(String line) throws SocketException, IOException {
		if (line.length() == 1) {
			// the server quits
			connection.close();
			appendMsg("\n!!!The server quits.\n\n");
			login = false;
			myLoginView.userListModel.removeAllElements();
			myLoginView.reset();
		} else {// other user quits
			removeUser(line.substring(2));
			appendMsg("\n" + "!!!Pity: " + line.substring(2)
					+ "disconnected from the game.\n\n");
			if (!myLoginView.active) {
				// TODO make the player offline in my the clientModel.
			}
		}
	}
}
